#include "client.hpp"
#ifndef _WIN32
#include <arpa/inet.h>
#include <netinet/in.h>
#include<fcntl.h>
#include<string.h>
#include <unistd.h>
#endif
#include<stdio.h>
#include<string>
#include<errno.h>

bool Client::emptysendback(Client*,void*){return true;}
Client::Client(DataPack defaultpack,TimeoutCallback timeout,void* timeoutarg,
time_t maxtime):maxtime(maxtime),pos(0),size(0),sendcount(0),recvcount(0),
_socket(-1),timeout(timeout),timeoutarg(timeoutarg),defaultpack(defaultpack),
sendpos(0),recvpos(0){}
Client::~Client(){
	if(_socket!=-1){
		#ifdef _WIN32
			closesocket(_socket);
		#else
			close(_socket);
		#endif
	}
}
size_t Client::getdata(char* buffer,size_t length){
	if(pos!=size){
		_thread_begin_recv:
		auto got=std::min(size-pos,length-recvpos);
		memcpy(buffer+recvpos,cache+pos,got);
		pos+=got;
		return got;
	}
	if(length>=CACHESIZE){
		auto back=recv(_socket,buffer+recvpos,length-recvpos,0);
		if(back>0)return back;
		return -1;
	}
	auto back=recv(_socket,cache,CACHESIZE,0);
	if(back>0){
		pos=0;
		size=back;
		goto _thread_begin_recv;
	}
	return -1;
}
#ifdef _WIN32
int Client::connecthost(const char* address,unsigned int port){
	std::string p(std::to_string(port));
	struct addrinfo hints;
	struct addrinfo *result=NULL;
	memset(&hints,0,sizeof(hints));
	hints.ai_family=AF_INET;
	hints.ai_socktype=SOCK_STREAM;
	hints.ai_protocol=IPPROTO_TCP;
	hints.ai_flags=AI_PASSIVE;
	auto res=getaddrinfo(0,p.c_str(),&hints,&result);
	if(res){
		printf("getaddrinfo error: %d\n",res);
		return -1;
	}
	auto s=socket(result->ai_family,result->ai_socktype,result->ai_protocol);
	if(s==INVALID_SOCKET){
		printf("socket create error\n");
		return -1;
	}
	static unsigned long NBMODE=1;
	ioctlsocket(s,FIONBIO,&NBMODE);
	if(_socket!=-1)closesocket(_socket);
	_socket=s;
	struct sockaddr_in addr{
		.sin_family=AF_INET,
		.sin_port=htons(port)
	};
	addr.sin_addr.s_addr=inet_addr(address);
	connect(s,(struct sockaddr*)&addr,sizeof(sockaddr_in));
	recordtime=time(nullptr);
	return 0;
}
#else
int Client::connecthost(const char* address,unsigned int port){
	int s=socket(AF_INET,SOCK_STREAM|SOCK_NONBLOCK,IPPROTO_TCP);
	if(s==-1){
		printf("socket create error:%d\n",errno);
		return -1;
	}
	if(_socket!=-1)close(_socket);
	_socket=s;
	struct sockaddr_in addr{
		.sin_family=AF_INET,
		.sin_port=htons(port)
	};
	addr.sin_addr.s_addr=inet_addr(address);
	connect(s,(struct sockaddr*)&addr,sizeof(sockaddr_in));
	recordtime=time(nullptr);
	return 0;
}
#endif
void Client::update(){
	auto& pendingrecv=this->pendingrecv;
	auto precv=pendingrecv.begin();
	auto curtime=time(nullptr);
	if(precv!=pendingrecv.end()){
		auto got=getdata(precv->src,precv->size);
		if(got!=-1){
			recvpos+=got;
			recordtime=curtime;
			if(recvpos==precv->size){
				recvpos=0;
				if(precv->callback(this,precv->arg)){
					pendingrecv.pop_front();
					recvcount--;
				}
			}
		}
		else if(curtime-recordtime>=maxtime){
			timeout(this,timeoutarg);
			pendingsend.clear();
			pendingrecv.clear();
			return;
		}
	}
	auto& pendingsend=this->pendingsend;
	auto psend=pendingsend.begin();
	auto& pack=this->defaultpack;
	auto size=pack.size;
	if(psend!=pendingsend.end()){
		auto back=send(_socket,psend->src+sendpos,psend->size-sendpos,0);
		if(back>0){
			recordtime=curtime;
			sendpos+=back;
			if(sendpos==psend->size){
				sendpos=0;
				if(psend->callback(this,psend->arg)){
					pendingsend.pop_front();
					sendcount--;
				}
			}
		}
		else if(curtime-recordtime>=maxtime){
			timeout(this,timeoutarg);
			pendingsend.clear();
			pendingrecv.clear();
			return;
		}
	}
	else if(size){
		auto back=send(_socket,(char*)pack.src+sendpos,size-sendpos,0);
		if(back>0){
			recordtime=curtime;
			sendpos+=back;
			if(sendpos==size){
				sendpos=0;
				pack.callback(this,pack.arg);
			}
		}
		else if(curtime-recordtime>=maxtime){
			timeout(this,timeoutarg);
			pendingsend.clear();
			pendingrecv.clear();
			return;
		}
	}
}
void Client::senddata(DataPack p){
	if(!sendcount){
		auto ret=send(_socket,p.src,p.size,0);
		if(ret==p.size){if(p.callback(this,p.arg))return;}
		else if(ret!=-1)sendpos=ret;
	}
	pendingsend.push_back(p);
	sendcount++;
}
void Client::recvdata(DataPack p){
	if(!recvcount){
		auto ret=getdata(p.src,p.size);
		if(ret==p.size){if(p.callback(this,p.arg))return;}
		else if(ret!=-1)recvpos=ret;
	}
	pendingrecv.push_back(p);
	recvcount++;
}
bool Client::openlib(){
	#ifdef _WIN32
	WSADATA wsaData;
	auto res=WSAStartup(MAKEWORD(2,2),&wsaData);
	if(res){
		printf("WSA start error %d\n",res);
		return false;
	}
	#endif
	return true;
}
void Client::closelib(){
	#ifdef _WIN32
	WSACleanup();
	#endif
}
size_t Client::getsent(){return sendpos;}
size_t Client::getrecv(){return recvpos;}